﻿Imports System.Runtime.InteropServices

Public Class KeyboardHook
    <DllImport("user32.dll", SetLastError:=True)>
    Private Shared Function GetAsyncKeyState(ByVal vKey As Integer) As Short
    End Function

    <DllImport("user32.dll", SetLastError:=True)>
    Private Shared Function SetWindowsHookEx(
        ByVal idHook As Integer,
        ByVal HookProc As KBDLLHookProc,
        ByVal hInstance As IntPtr,
        ByVal wParam As Integer) As Integer
    End Function

    <DllImport("user32.dll", SetLastError:=True)>
    Private Shared Function CallNextHookEx(
        ByVal idHook As Integer,
        ByVal nCode As Integer,
        ByVal wParam As IntPtr,
        ByVal lParam As IntPtr) As Integer
    End Function

    <DllImport("user32.dll", SetLastError:=True)>
    Private Shared Function UnhookWindowsHookEx(ByVal idHook As Integer) As Boolean
    End Function

    <StructLayout(LayoutKind.Sequential)>
    Private Structure KBDLLHOOKSTRUCT
        Public vkCode As UInt32
        Public scanCode As UInt32
        Public flags As KBDLLHOOKSTRUCTFlags
        Public time As UInt32
        Public dwExtraInfo As UIntPtr
    End Structure

    <Flags()>
    Private Enum KBDLLHOOKSTRUCTFlags As UInt32
        LLKHF_EXTENDED = &H1
        LLKHF_INJECTED = &H10
        LLKHF_ALTDOWN = &H20
        LLKHF_UP = &H80
    End Enum

    Public Shared Event KeyDown(ByVal WPFKey As Key)
    Public Shared Event KeyUp(ByVal WPFKey As Key)

    Private Delegate Function KBDLLHookProc(
        ByVal nCode As Integer,
        ByVal wParam As IntPtr,
        ByVal lParam As IntPtr) As Integer

    Private Const WH_KEYBOARD_LL As Integer = 13
    Private Const HC_ACTION As Integer = 0
    Private Const WM_KEYDOWN As Integer = &H100
    Private Const WM_KEYUP As Integer = &H101
    Private Const WM_SYSKEYDOWN As Integer = &H104
    Private Const WM_SYSKEYUP As Integer = &H105

    Private HHookID As IntPtr = IntPtr.Zero
    Private kbdHookDelegate As KBDLLHookProc = New KBDLLHookProc(AddressOf KeyboardProc)

    ''' <summary>
    ''' 键盘钩子的回调函数
    ''' </summary>
    ''' <param name="nCode">处理消息的代码</param>
    ''' <param name="wParam">按键消息生成的虚拟键码</param>
    ''' <param name="lParam">由按键生成消息的额外信息</param>
    ''' <returns>Integer</returns>
    Private Function KeyboardProc(ByVal nCode As Integer,
                                  ByVal wParam As IntPtr,
                                  ByVal lParam As IntPtr) As Integer
        If (nCode = HC_ACTION) Then
            Dim udtKBHook As KBDLLHOOKSTRUCT
            Select Case wParam
                Case WM_KEYDOWN, WM_SYSKEYDOWN
                    RaiseEvent KeyDown(KeyInterop.KeyFromVirtualKey(CType(Marshal.PtrToStructure(lParam, udtKBHook.GetType()), KBDLLHOOKSTRUCT).vkCode))
                Case WM_KEYUP, WM_SYSKEYUP
                    RaiseEvent KeyUp(KeyInterop.KeyFromVirtualKey(CType(Marshal.PtrToStructure(lParam, udtKBHook.GetType()), KBDLLHOOKSTRUCT).vkCode))
            End Select
        End If
        Return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam)
    End Function

    ''' <summary>
    ''' 构造函数
    ''' </summary>
    Public Sub New()
        HHookID = SetWindowsHookEx(WH_KEYBOARD_LL,
                                   kbdHookDelegate,
                                   Marshal.GetHINSTANCE(Reflection.Assembly.GetExecutingAssembly.GetModules()(0)), 0)
        If HHookID = IntPtr.Zero Then
            Throw New Exception("键盘钩子安装失败!")
        End If
    End Sub

    ''' <summary>
    ''' 同步获取按键状态
    ''' </summary>
    ''' <param name="VirtualKey">虚拟键码</param>
    ''' <returns>Short</returns>
    Public Function GetKS(ByVal VirtualKey As Integer) As Short
        Return GetAsyncKeyState(VirtualKey)
    End Function

    ''' <summary>
    ''' 析构函数
    ''' </summary>
    Protected Overrides Sub Finalize()
        If Not HHookID = IntPtr.Zero Then
            UnhookWindowsHookEx(HHookID)
        End If
        MyBase.Finalize()
    End Sub
End Class